package org.jflame.web.filter;

import org.jflame.commons.config.ConfigHolder;
import org.jflame.commons.config.ConfigReader;
import org.jflame.commons.file.FileHelper;
import org.jflame.commons.util.ArrayHelper;
import org.jflame.commons.util.CollectionHelper;
import org.jflame.commons.util.StringHelper;
import org.jflame.commons.valid.Validators;
import org.jflame.web.FilterParamConfig;
import org.jflame.web.WebUtils;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * csrf攻击拦截，比对请求来源referer.
 * <p>
 * 配置参数
 * 
 * <pre>
 * error-page 错误转向页面
 * white-file 白名单文件
 * white-urls 白名单地址,多个逗号分隔
 * </pre>
 * 
 * 在Properties文件配置,属性名: jflame.web.csrf.[filter属性名] (如:white-file --&gt; jflame.web.csrf.white-file)
 * 
 * @author charles.zhang
 */
public class CsrfFilter extends IgnoreUrlMatchFilter {

    // private final String[] cfgKeys = { "error-page","white-file","white-urls" };
    // private final String propKeyPrefix = ConfigReader.JFLAME_CFG_KEY_PREFIX + ".web.csrf-";
    private final String headerReferer = "Referer";

    private Set<String> whiteUrls = new HashSet<>(); // 白名单
    private String errorPage;// 错误转向页面
    private CsrfProperties cfg;

    protected final void doInternalFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws ServletException, IOException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        // 获取请求url地址
        String referurl = request.getHeader(headerReferer);
        if (isWhiteReq(referurl, request)) {
            chain.doFilter(request, response);
        } else {
            String url = request.getRequestURL()
                    .toString();
            log.warn("非法请求来源:ip={},url={},referer={}", WebUtils.getRemoteClientIP(request), url, referurl);
            if (StringHelper.isNotEmpty(errorPage)) {
                request.getRequestDispatcher(errorPage)
                        .forward(request, response);
            } else {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "非法请求来源");
            }
            return;
        }
    }

    /*
     * 判断是否是白名单
     */
    private boolean isWhiteReq(String referUrl, HttpServletRequest request) {
        boolean isSafeUri = false;
        if (StringHelper.isEmpty(referUrl)) {
            isSafeUri = true;
        } else {
            URI refererUri = URI.create(referUrl);
            // 只比较域名
            if (refererUri.getHost()
                    .equals(request.getServerName())) {
                isSafeUri = true;
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("referer:[host={},port={}],request:[host={},port={}]", refererUri.getHost(),
                            refererUri.getPort(), request.getServerName(), request.getServerPort());
                }
                if (CollectionHelper.isNotEmpty(whiteUrls)) {
                    for (String urlRegex : whiteUrls) {
                        if (Validators.regex(referUrl, urlRegex)) {
                            isSafeUri = true;
                            break;
                        }
                    }
                }
            }
        }

        return isSafeUri;
    }

    /*@Override
    protected void doInternalInit(FilterConfig filterConfig) throws ServletException {
        ConfigReader filterParam = new FilterParamConfig(filterConfig);
        String whiteFile;
    
        List<String> tmpWhiteUrls;
    
        errorPage = filterParam.getString(cfgKeys[0]);
        whiteFile = filterParam.getString(cfgKeys[1]);
        tmpWhiteUrls = filterParam.getStringList(cfgKeys[2]);
        // 从配置文件里读
        if (StringHelper.isEmpty(whiteFile) && CollectionHelper.isEmpty(tmpWhiteUrls)
                && ConfigHolder.getConfig() != null) {
            errorPage = ConfigHolder.getString(propKeyPrefix + cfgKeys[0]);
            whiteFile = ConfigHolder.getString(propKeyPrefix + cfgKeys[1]);
            tmpWhiteUrls = ConfigHolder.getStrings(propKeyPrefix + cfgKeys[2]);
        }
        if (CollectionHelper.isNotEmpty(tmpWhiteUrls)) {
            whiteUrls.addAll(tmpWhiteUrls);
        }
        if (StringHelper.isNotEmpty(whiteFile)) {
            try {
                tmpWhiteUrls = FileHelper.readLines(whiteFile, StandardCharsets.UTF_8);
                if (CollectionHelper.isNotEmpty(tmpWhiteUrls)) {
                    whiteUrls.addAll(tmpWhiteUrls);
                }
            } catch (IOException e) {
                log.error("csrf白名单读取失败" + whiteFile, e);
            }
        }
    
    }*/

    @Override
    protected void doInternalInit(FilterConfig filterConfig) throws ServletException {
        cfg = new CsrfProperties();
        boolean hasVal = new FilterParamConfig(filterConfig).bindBean(cfg, null);
        if (!hasVal && ConfigHolder.getConfig() != null) {
            ConfigHolder.getConfig()
                    .bindBean(cfg, CsrfProperties.PREFIX);
        }
        if (ArrayHelper.isNotEmpty(cfg.getWhiteUrls())) {
            Collections.addAll(whiteUrls, cfg.getWhiteUrls());
        }
        if (StringHelper.isNotEmpty(cfg.getWhiteFile())) {
            try {
                List<String> tmpWhiteUrls = FileHelper.readLines(cfg.getWhiteFile(), StandardCharsets.UTF_8);
                if (CollectionHelper.isNotEmpty(tmpWhiteUrls)) {
                    whiteUrls.addAll(tmpWhiteUrls);
                }
            } catch (IOException e) {
                log.error("csrf白名单读取失败" + cfg.getWhiteFile(), e);
            }
        }
    }

    public static class CsrfProperties {

        public static final String PREFIX = ConfigReader.JFLAME_CFG_KEY_PREFIX + ".web.csrf.";
        private String errorPage;
        private String whiteFile;
        private String[] whiteUrls;

        public String getErrorPage() {
            return errorPage;
        }

        public void setErrorPage(String errorPage) {
            this.errorPage = errorPage;
        }

        public String getWhiteFile() {
            return whiteFile;
        }

        public void setWhiteFile(String whiteFile) {
            this.whiteFile = whiteFile;
        }

        public String[] getWhiteUrls() {
            return whiteUrls;
        }

        public void setWhiteUrls(String[] whiteUrls) {
            this.whiteUrls = whiteUrls;
        }

    }

}
